#include <assert.h>
#include <stdlib.h>

#include <common/macro/build.h>
#include <common/macro/debug.h>

#include <transaction/transactionlist.h>
#include <transaction/transactionlog.h>

API_LIBLOCAL transaction_list_t *transaction_list_new() {
	transaction_list_t *transaction_list;
	
	transaction_list = malloc(sizeof(*transaction_list));

	transaction_list->phases = NULL;
	transaction_list->phases_c = 0;

	transaction_list->first_id = 0;
	transaction_list->last_id = 0;
	transaction_list->vacuum_id = 0;

	return transaction_list;
}

API_LIBLOCAL void transaction_list_destroy(transaction_list_t *transaction_list) {
	if (transaction_list == NULL) return;

	free(transaction_list->phases);
	free(transaction_list);
}

API_LIBLOCAL int transaction_list_refresh(transaction_list_t *transaction_list, transaction_log_t *transaction_log) {
	assert(transaction_list != NULL);

	free(transaction_list->phases);

	transaction_list->phases = transaction_log_dump(transaction_log, &transaction_list->first_id, &transaction_list->last_id);

	transaction_log_readlock_vacuum(transaction_log);
	transaction_log_get_vacuum(transaction_log, &transaction_list->vacuum_id);
	transaction_log_unlock_vacuum(transaction_log);
	transaction_list->wraparound = transaction_log->maxitems;

	return 0;
}

API_LIBLOCAL int transaction_list_compare_ids(transaction_list_t *transaction_list, transaction_id_t id1, transaction_id_t id2) {
	/* Vacuum ID is the border of wraparound, it becomes the new zero. */

	/*
	 * Case A: IDs are equal.
	 */
	debugmsg("Comparing #%d and #%d (vacuum ID=%d).\n", id1, id2, transaction_list->vacuum_id);
	if (id1 == id2) {
		debugmsg("Case A: Equal.\n");
		return 0; 
	}

	/*
	 * Case B: Both IDs are >= wraparound vacuum ID. Simply compare them.
	 */
	if (id1 >= transaction_list->vacuum_id && id2 >= transaction_list->vacuum_id) {
		debugmsg("Case B: Equal range.\n");
		if (id1 < id2) return -1;
		if (id1 > id2) return 1;
		return 0;
	}

	/*
	 * Case C: ID1 is >= wraparound vacuum ID, ID2 is < vacuum ID. In that case,
	 * ID2 is greater than ID1, because it is further in the future.
	 */
	if (id1 >= transaction_list->vacuum_id && id2 < transaction_list->vacuum_id) {
		debugmsg("Case C: Wraparound tail.\n");
		return -1;
	}

	/*
	 * Case D: ID1 is < wraparound vacuum ID, ID2 is >= vacuum ID. In that case,
	 * ID2 is lower than ID1, because it is further in the future.
	 */
	if (id1 < transaction_list->vacuum_id && id2 >= transaction_list->vacuum_id) {
		debugmsg("Case C: Wraparound head.\n");
		return 1;
	}

	/*
	 * Case E: ID1 and ID2 are both < wraparound vacuum ID. Simply compare them.
	 */
	if (id1 < transaction_list->vacuum_id && id2 < transaction_list->vacuum_id) {
		debugmsg("Case E: Equal range.\n");
		if (id1 < id2) return -1;
		if (id1 > id2) return 1;
		return 0;
	}

	/* This shouldn't happen. */
	assert(1 == 0);
	return 0;
}

API_LIBLOCAL transaction_id_t transaction_list_last_finished(transaction_list_t *transaction_list) {
	transaction_id_t id;
	assert(transaction_list != NULL);

	debugmsg("Asking for last finished ID on range [%d:%d].\n", transaction_list->first_id, transaction_list->last_id);
	
	id = transaction_list->first_id;
	while (id != transaction_list->last_id) {
		if (transaction_list->phases[id % transaction_list->wraparound] != COMMITED && 
		  transaction_list->phases[id % transaction_list->wraparound] != ABORTED) {
			debugmsg("Querying #%d (%c): YES!\n", id, transaction_log_phase_to_char(transaction_list->phases[id % transaction_list->wraparound]));
			return id - 1;
		}
		debugmsg("Querying #%d (%c): NO!\n", id, transaction_log_phase_to_char(transaction_list->phases[id % transaction_list->wraparound]));

		id++;
	}

	/* All transactions in the list are currently commited, return last in the list. */
	return transaction_list->last_id;
}

API_LIBLOCAL int transaction_list_query(transaction_list_t *transaction_list, transaction_id_t transaction_id, transaction_phase_e *phase) {
	assert(transaction_list != NULL);
	assert(phase != NULL);

	debugmsg("Querying status of #%d\n", transaction_id);
	if (transaction_list->first_id <= transaction_list->last_id) {
		/*
		 * Cases without wraparound.
		 */
		debugmsg("No wraparound.\n");

		/*
		 * Case A: Vacuum ID is lower than the first ID tracked by the log. This
		 * means that there is PAST (between vacuum ID and first_id).
		 */
		if (transaction_list->vacuum_id < transaction_list->first_id) { 
			debugmsg("Case A\n");
			if (transaction_id >= transaction_list->first_id && transaction_id <= transaction_list->last_id) {
				debugmsg("CURRENT\n");
				/* CURRENT, verify from list. */
				*phase = transaction_list->phases[transaction_id % transaction_list->wraparound];
				return 1;
			}
	
			if (transaction_id >= transaction_list->vacuum_id && transaction_id < transaction_list->first_id) {
				debugmsg("PAST\n");
				/* PAST. */
				return 0;
			}
	
			/* Else, FUTURE. */ 
			debugmsg("FUTURE\n");
			*phase = ACTIVE;
			return 1;
		}

		/*
		 * Case B: Vacuum ID is higher than the last ID tracked by the log. Future
		 * is between those two.
		 */
		if (transaction_list->vacuum_id > transaction_list->last_id) {
			debugmsg("Case B\n");
			if (transaction_id >= transaction_list->first_id && transaction_id <= transaction_list->last_id) {
				debugmsg("CURRENT\n");
				/* CURRENT, verify from list. */
				*phase = transaction_list->phases[transaction_id % transaction_list->wraparound];
				return 1;
			}
	
			if (transaction_id < transaction_list->vacuum_id && transaction_id > transaction_list->last_id) {
				/* FUTURE. */
				debugmsg("FUTURE\n");
				*phase = ACTIVE;
				return 1;
			}

			debugmsg("PAST\n");
			/* Else, PAST. */ 
			return 0;
		}

		/*
		 * Case C: Vacuum ID is covered in the transaction log. There is
		 * no PAST, only FUTURE and CURRENT.
		 */
		if (transaction_list->vacuum_id >= transaction_list->first_id && transaction_list->vacuum_id <= transaction_list->last_id) {
			debugmsg("Case C: %u >= %u && %u <= %u\n", transaction_list->vacuum_id, transaction_list->first_id, transaction_list->vacuum_id, transaction_list->last_id);
			if (transaction_id >= transaction_list->first_id && transaction_id <= transaction_list->last_id) {
				/* CURRENT, verify from list. */
				debugmsg("CURRENT\n");
				*phase = transaction_list->phases[transaction_id % transaction_list->wraparound];
				return 1;
			}

			debugmsg("FUTURE\n");
			/* Else, FUTURE. */
			*phase = ACTIVE;
			return 1;
		}
	 } else {
	 	/*
		 * Cases with wraparound.
		 */
		debugmsg("Wraparound present\n");

		/*
		 * Case D: Vacuum ID is higher than the last ID tracked by the log.
		 */
		if (transaction_list->last_id < transaction_list->vacuum_id) {
			debugmsg("Case D\n");
			if (transaction_id >= transaction_list->first_id || transaction_id <= transaction_list->last_id) {
				/* CURRENT, verify from list. */
				debugmsg("CURRENT\n");
				*phase = transaction_list->phases[transaction_id % transaction_list->wraparound];
				return 1;
			}
			
			if (transaction_id > transaction_list->last_id) {
				/* FUTURE. */
				debugmsg("FUTURE\n");
				*phase = ACTIVE;
				return 1;
			}

			/* Else, PAST. */
			debugmsg("PAST\n");
			return 0;
		}

		/*
		 * Case E: Vacuum ID is between first ID and the maximum value
		 * of the datatype. There is no PAST.
		 */
		if (transaction_list->vacuum_id >= transaction_list->first_id) {
			debugmsg("Case E\n");
			if (transaction_id >= transaction_list->first_id || transaction_id <= transaction_list->last_id) {
				/* CURRENT, verify from list. */
				debugmsg("CURRENT\n");
				*phase = transaction_list->phases[transaction_id % transaction_list->wraparound];
				return 1;
			}
			
			/* Else, FUTURE. */
			debugmsg("FUTURE\n");
			return 0;
		}

		/*
		 * Case F: Vacuum ID is between 0 and the last ID. There is no PAST.
		 */
		if (transaction_list->vacuum_id <= transaction_list->last_id) {
			debugmsg("Case F\n");
			if (transaction_id >= transaction_list->first_id || transaction_id <= transaction_list->last_id) {
				/* CURRENT, verify from list. */
				debugmsg("CURRENT\n");
				*phase = transaction_list->phases[transaction_id % transaction_list->wraparound];
				return 1;
			}
			
			/* Else, FUTURE. */
			debugmsg("FUTURE\n");
			return 0;
		}
	}
	
	return 0;
}
